package main;

import handlers.KeyHandler;

import java.net.URL;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.logging.Level;
import java.util.logging.Logger;

import mapControl.GraphicsMapControl;

import com.jme.bounding.BoundingBox;
import com.jme.input.ChaseCamera;
import com.jme.input.InputHandler;
import com.jme.input.KeyInput;
import com.jme.input.action.InputAction;
import com.jme.input.action.InputActionEvent;
import com.jme.input.thirdperson.ThirdPersonMouseLook;
import com.jme.light.DirectionalLight;
import com.jme.math.FastMath;
import com.jme.math.Vector3f;
import com.jme.renderer.ColorRGBA;
import com.jme.renderer.Renderer;
import com.jme.scene.Geometry;
import com.jme.scene.Line;
import com.jme.scene.Node;
import com.jme.scene.Spatial;
import com.jme.scene.Text;
import com.jme.scene.Spatial.LightCombineMode;
import com.jme.scene.Spatial.TextureCombineMode;
import com.jme.scene.state.LightState;
import com.jme.scene.state.TextureState;
import com.jme.scene.state.ZBufferState;
import com.jme.scene.state.ZBufferState.TestFunction;
import com.jme.system.DisplaySystem;
import com.jmex.audio.AudioSystem;
import com.jmex.audio.AudioTrack;
import com.jmex.audio.AudioTrack.TrackType;
import com.jmex.physics.PhysicsNode;
import com.jmex.physics.util.SimplePhysicsGame;

import elementos.Floor;
import elementos.Player;
import enemies.Bad_Boys;

public class JDM extends SimplePhysicsGame {

	GraphicsMapControl mapControl;
	Player player;

	Queue<PhysicsNode> trashColector;
	private ChaseCamera chaseCamera;
	private int indexObjAct;

	private Text player1ScoreText;
	private Vector3f center;
	private int spacing;
	private Bad_Boys IA;

	public static void main(String[] args) throws Exception {

		Logger.getLogger("").setLevel(Level.WARNING); // to see the important
		// stuff

		JDM game = new JDM();
		game.start();

	}

	@Override
	protected void simpleInitGame() {
		
		getPhysicsSpace().setDirectionalGravity(new Vector3f(0, -50, 0));
		trashColector = new ConcurrentLinkedQueue<PhysicsNode>();
		mapControl = new GraphicsMapControl(21, 21, 5, rootNode,
				getPhysicsSpace(), display, trashColector);
		;
		mapControl.buildMap();
		new Floor("floor", mapControl);
		player = new Player("player", mapControl);

		buildInput();
		buildLights();

		buildChaseCamera();
		buildInfo();

		buildIA();

		// remove comment for debug graphics and physics
		// rootNode.attachChild(buildGeometry());
		// showPhysics = true;
		// setup a music score for our demo
		
		AudioSystem audio = AudioSystem.getSystem();
		AudioTrack targetSound = audio.createAudioTrack(getClass().getResource(
        "/music/loop.wav"), false);
		targetSound.setVolume(1.0f);	
		targetSound.play();
		targetSound.setLooping(true);
	}

	private AudioTrack getMusic(URL resource) {
		// Create a non-streaming, non-looping, relative sound clip.
		AudioTrack sound = AudioSystem.getSystem().createAudioTrack(resource,
				true);
		sound.setType(TrackType.MUSIC);
		sound.setRelative(true);
		sound.setTargetVolume(0.7f);
		sound.setLooping(false);
		return sound;
	}

	private void buildIA() {
		IA = new Bad_Boys(mapControl, player);
	}

	@Override
	protected void simpleRender() {
		chaseCamera.update(tpf);
		if (!trashColector.isEmpty()) {
			trashColector.remove().delete();
		}
	}

	@Override
	protected void simpleUpdate() {
		if (player.getBodyController().getActiveAnimation().isCyleComplete()) {
			player.getBodyController().fadeTo("idle", 1, false);
		}

		player1ScoreText.getText().replace(0,
				player1ScoreText.getText().length(),
				"" + player.getRow() + " " + player.getCol());

		player.getNode().rotateUpTo(new Vector3f(0, 1, 0));


	}

	private void buildInfo() {
		player1ScoreText = Text.createDefaultTextLabel("player1ScoreText", "0");
		player1ScoreText.setRenderQueueMode(Renderer.QUEUE_ORTHO);
		player1ScoreText.setLightCombineMode(Spatial.LightCombineMode.Off);
		player1ScoreText.setLocalTranslation(new Vector3f(0, display
				.getHeight() / 2, 1));
		rootNode.attachChild(player1ScoreText);

	}

	private void buildLights() {
		/** Set up a basic, default light. */
		DirectionalLight light = new DirectionalLight();
		light.setDiffuse(new ColorRGBA(1.0f, 1.0f, 1.0f, 1.0f));
		light.setAmbient(new ColorRGBA(0.5f, 0.5f, 0.5f, .5f));
		light.setDirection(new Vector3f(1, -1, 0));
		light.setShadowCaster(true);
		light.setEnabled(true);

		/** Attach the light to a lightState and the lightState to rootNode. */
		LightState lightState = display.getRenderer().createLightState();
		lightState.setEnabled(true);
		lightState.setGlobalAmbient(new ColorRGBA(.2f, .2f, .2f, 1f));
		lightState.attach(light);
		rootNode.setRenderState(lightState);

	}

	private void buildChaseCamera() {
		HashMap<String, Object> props = new HashMap<String, Object>();
		props.put(ChaseCamera.PROP_INITIALSPHERECOORDS, new Vector3f(10, 10,
				60 * FastMath.DEG_TO_RAD));
		props.put(ChaseCamera.PROP_DAMPINGK, "4");
		props.put(ChaseCamera.PROP_SPRINGK, "9");
		props.put(ThirdPersonMouseLook.PROP_ENABLED, "false");
		chaseCamera = new ChaseCamera(cam, player.getNode(), props);
		chaseCamera.setMaxDistance(80);
		chaseCamera.setMinDistance(60);
	}

	private void buildInput() {

		input = new KeyHandler(player, mapControl);

		InputAction cambiarObjetivo = new InputAction() {

			public void performAction(InputActionEvent evt) {
				if (evt.getTriggerPressed()) {
					Spatial objetivo = chaseCamera.getTarget();

					if (objetivo == player.getNode()) {
						indexObjAct = 0;
						objetivo = IA.getCollectionEnemies().get(indexObjAct)
								.getNode();
					} else if (++indexObjAct < IA.getCollectionEnemies().size()) {

						objetivo = IA.getCollectionEnemies().get(indexObjAct)
								.getNode();

					} else {
						objetivo = player.getNode();
					}

					chaseCamera.setTarget(objetivo);
					System.out.println("obj: " + objetivo.getName());
				}

			}

		};

		InputAction farCam = new InputAction() {

			public void performAction(InputActionEvent evt) {
				if (evt.getTriggerPressed()) {
					chaseCamera
							.setMaxDistance(chaseCamera.getMaxDistance() + 10);
					chaseCamera
							.setMinDistance(chaseCamera.getMinDistance() + 10);
				}
			}
		};

		InputAction nearCam = new InputAction() {

			public void performAction(InputActionEvent evt) {
				if (evt.getTriggerPressed()) {
					chaseCamera
							.setMaxDistance(chaseCamera.getMaxDistance() - 10);
					chaseCamera
							.setMinDistance(chaseCamera.getMinDistance() - 10);

				}
			}
		};

		input.addAction(farCam, InputHandler.DEVICE_KEYBOARD,
				KeyInput.KEY_PGUP, InputHandler.AXIS_NONE, false);

		input.addAction(nearCam, InputHandler.DEVICE_KEYBOARD,
				KeyInput.KEY_PGDN, InputHandler.AXIS_NONE, false);

		input.addAction(cambiarObjetivo, InputHandler.DEVICE_KEYBOARD,
				KeyInput.KEY_DELETE, InputHandler.AXIS_NONE, false);

	}

	// -----------------------------------------------------------------------------------
	// Debug
	// -----------------------------------------------------------------------------------

	/**
	 * If drawBehind is false, the grid is tested with the z-buffer, which means
	 * that only the visible parts of the grid will be seen and objects in front
	 * of the grid will hide it.
	 */
	protected boolean drawBehind = false;

	/**
	 * If drawAxis is true, Red, Green and Blue axis are drawn for the XYZ axis.
	 * Axis are never rotated, so they are always world axis, but they are
	 * translated to the grid center.
	 */
	protected boolean drawAxis = true;

	/**
	 * Center of the grid.
	 */

	/**
	 * Space between grid lines.
	 */

	/**
	 * Draw a bold line every given number of lines.
	 */
	protected int marker = 5;

	/**
	 * Grid rotation. This is an Euler rotation (X,Y,Z). Values are multiplied
	 * by PI before being applied.
	 */
	protected final Vector3f rotatePi = new Vector3f();
	private float extent;

	/**
	 * Grid extent. The extent is applied towards the positive and also negative
	 * sides of the grid.
	 */

	/**
	 * Creates the grid. The grid is returned as a Node.
	 */
	protected Node buildGeometry() {
		center = new Vector3f(
				((mapControl.getWide() * mapControl.getTilesize()) / 2), 5,
				((mapControl.getHight() * mapControl.getTilesize()) / 2));
		getPhysicsSpace().setDirectionalGravity(new Vector3f(0, -90f, 0));

		spacing = mapControl.getTilesize();

		extent = 100;

		Node grid = new Node("grid");
		Node axis = new Node("axis");
		Node axisGrid = new Node("axisGrid");

		// Create Grid
		ArrayList<Vector3f> markerVertices = new ArrayList<Vector3f>();
		ArrayList<Vector3f> regularVertices = new ArrayList<Vector3f>();
		for (int i = 0; i * spacing <= extent; i++) {

			if (i % marker > 0) {
				// Normal line
				regularVertices.add(new Vector3f(-extent, 0, i * spacing));
				regularVertices.add(new Vector3f(extent, 0, i * spacing));
				regularVertices.add(new Vector3f(-extent, 0, -i * spacing));
				regularVertices.add(new Vector3f(extent, 0, -i * spacing));
				regularVertices.add(new Vector3f(i * spacing, 0, -extent));
				regularVertices.add(new Vector3f(i * spacing, 0, extent));
				regularVertices.add(new Vector3f(-i * spacing, 0, -extent));
				regularVertices.add(new Vector3f(-i * spacing, 0, extent));
			} else {
				// Marker line
				markerVertices.add(new Vector3f(-extent, 0, i * spacing));
				markerVertices.add(new Vector3f(extent, 0, i * spacing));
				markerVertices.add(new Vector3f(-extent, 0, -i * spacing));
				markerVertices.add(new Vector3f(extent, 0, -i * spacing));
				if (i != 0) {
					markerVertices.add(new Vector3f(i * spacing, 0, -extent));
					markerVertices.add(new Vector3f(i * spacing, 0, extent));
					markerVertices.add(new Vector3f(-i * spacing, 0, -extent));
					markerVertices.add(new Vector3f(-i * spacing, 0, extent));
				}
			}

		}

		Geometry regularGrid = new Line("regularLine", regularVertices
				.toArray(new Vector3f[] {}), null, null, null);
		regularGrid.getDefaultColor().set(ColorRGBA.darkGray);
		grid.attachChild(regularGrid);
		Geometry markerGrid = new Line("markerLine", markerVertices
				.toArray(new Vector3f[] {}), null, null, null);
		regularGrid.getDefaultColor().set(ColorRGBA.lightGray);
		grid.attachChild(markerGrid);

		grid.getLocalRotation().fromAngles(FastMath.PI * rotatePi.x,
				FastMath.PI * rotatePi.y, FastMath.PI * rotatePi.z);

		axisGrid.attachChild(grid);

		// Create Axis

		if (drawAxis) {

			Vector3f xAxis = new Vector3f(extent, 0, 0); // red
			Vector3f yAxis = new Vector3f(0, extent, 0); // green
			Vector3f zAxis = new Vector3f(0, 0, extent); // blue

			ColorRGBA[] red = new ColorRGBA[2];
			red[0] = new ColorRGBA(ColorRGBA.red);
			red[1] = new ColorRGBA(ColorRGBA.red);

			ColorRGBA[] green = new ColorRGBA[2];
			green[0] = new ColorRGBA(ColorRGBA.green);
			green[1] = new ColorRGBA(ColorRGBA.green);

			ColorRGBA[] blue = new ColorRGBA[2];
			blue[0] = new ColorRGBA(ColorRGBA.blue);
			blue[1] = new ColorRGBA(ColorRGBA.blue);

			Line lx = new Line("xAxis",
					new Vector3f[] { xAxis.negate(), xAxis }, null, red, null);
			Line ly = new Line("yAxis",
					new Vector3f[] { yAxis.negate(), yAxis }, null, green, null);
			Line lz = new Line("zAxis",
					new Vector3f[] { zAxis.negate(), zAxis }, null, blue, null);

			lx.setModelBound(new BoundingBox()); // Important to set bounds to
			// prevent some error
			lx.updateModelBound();
			ly.setModelBound(new BoundingBox()); // Important to set bounds to
			// prevent some error
			ly.updateModelBound();
			lz.setModelBound(new BoundingBox()); // Important to set bounds to
			// prevent some error
			lz.updateModelBound();

			axis.attachChild(lx);
			axis.attachChild(ly);
			axis.attachChild(lz);

			axisGrid.attachChild(axis);

		}

		// RenderStates for the whole grid and axis

		TextureState ts = DisplaySystem.getDisplaySystem().getRenderer()
				.createTextureState();
		axisGrid.setTextureCombineMode(TextureCombineMode.Off);
		axisGrid.setRenderState(ts);

		ZBufferState zs = DisplaySystem.getDisplaySystem().getRenderer()
				.createZBufferState();
		zs.setFunction(drawBehind ? TestFunction.Always
				: TestFunction.LessThanOrEqualTo);
		zs.setWritable(false);
		zs.setEnabled(true);
		axisGrid.setRenderState(zs);

		axisGrid.setLightCombineMode(LightCombineMode.Off);

		axisGrid.updateRenderState();

		axisGrid.getLocalTranslation().set(center);
		axisGrid.updateGeometricState(0, true);

		axisGrid.lock();

		return axisGrid;
	}

}